#include "string_test_base.h"

TEST(bp_unicode_tests, u16_next)
{
	auto tables = createTestUnicodeTable();
	auto u16vec = utf16_test_string::unicodeTablesToTestUTF16StringList(tables);
	for (auto u16vec_iter = u16vec.begin(); u16vec_iter != u16vec.end(); ++u16vec_iter)
	{
		bp_char32_t value;
		EXPECT_EQ(nullptr, bp_u16next(nullptr, u16vec_iter->size(), u16vec_iter->byte_order, &value));
		EXPECT_EQ(nullptr, bp_u16next(u16vec_iter->ptr(), 0, u16vec_iter->byte_order, &value));

		bp_char32_t *outputList[] = {&value, nullptr};
		for (size_t i = 0; i < sizeof(outputList) / sizeof(*outputList); ++i)
		{
			bp_char32_t *outputValue = outputList[i];
			size_t offset = 0;
			for (auto iter = tables.begin(); iter != tables.end(); ++iter)
			{
				auto next = bp_u16next(u16vec_iter->ptr() + offset, u16vec_iter->size() - offset, u16vec_iter->byte_order, outputValue);
				auto expectNext = u16vec_iter->ptr() + offset + iter->u16.size();
				if (u16vec_iter->withbom && offset == 0)
				{
					expectNext += u16vec_iter->bomHeaderSize();
				}
				EXPECT_EQ(expectNext, next);

				if (outputValue)
				{
					EXPECT_EQ(iter->u16char, *outputValue);
				}

				offset += next - u16vec_iter->ptr() - offset;
			}
		}
	}
}

TEST(bp_unicode_tests, u16_encode_null_input)
{
	bp_char32_t u16;
	EXPECT_EQ(2, bp_u16encode(0x10FFFF, true, nullptr));
	EXPECT_EQ(2, bp_u16encode(0x10FFFF, true, &u16));
}

TEST(bp_unicode_tests, utf16_encode)
{
	bp_char32_t u16char;
	EXPECT_EQ(1, bp_u16encode(0, true, &u16char));
	EXPECT_EQ(0, u16char);

	EXPECT_EQ(0, bp_u16encode(0x110000, true, &u16char));

	auto tables = createTestUnicodeTable();
	for (auto iter = tables.begin(); iter != tables.end(); ++iter)
	{
		size_t encodedCount = bp_u16encode(iter->codepoint, true, &u16char);
		EXPECT_EQ(iter->u16.size(), encodedCount);
		EXPECT_EQ(iter->u16char, u16char);
	}

	for (auto iter = tables.begin(); iter != tables.end(); ++iter)
	{
		size_t encodedCount = bp_u16encode(iter->bigEndianCodepoint, false, &u16char);
		EXPECT_EQ(iter->u16.size(), encodedCount);
		EXPECT_EQ(iter->u16char, u16char);
	}
}

TEST(bp_unicode_tests, utf16_decode_null_input)
{
	bp_char16_t u16 = 0x41;
	bp_char32_t codepoint = 0;
	EXPECT_EQ(nullptr, bp_u16next_codepoint(&u16, 0, bp_little_endian, &codepoint));
	EXPECT_EQ(&u16 + 1, bp_u16next_codepoint(&u16, 1, bp_little_endian, nullptr));
	EXPECT_EQ(nullptr, bp_u16next_codepoint(nullptr, 1, bp_little_endian, &codepoint));
}

TEST(bp_unicode_tests, utf16_decode_surrogates)
{
	bp_char32_t codepoint;
	bp_char16_t u16chars[] = {0xD800, 0xDC00};
	EXPECT_EQ(u16chars + 2, bp_u16next_codepoint(u16chars, 2, bp_little_endian, &codepoint));
	EXPECT_EQ(0x10000, codepoint);

	EXPECT_EQ(u16chars + 1, bp_u16next_codepoint(u16chars, 1, bp_little_endian, &codepoint));
	EXPECT_EQ(u16chars[0], codepoint);
	EXPECT_EQ(u16chars + 2, bp_u16next_codepoint(u16chars + 1, 1, bp_little_endian, &codepoint));
	EXPECT_EQ(u16chars[1], codepoint);

	//(\uD800 - 1)\uDC00
	u16chars[0] = 0xD7FF;
	u16chars[1] = 0xDFFF;
	EXPECT_EQ(u16chars + 1, bp_u16next_codepoint(u16chars, 2, bp_little_endian, &codepoint));
	EXPECT_EQ(0xD7FF, codepoint);

	//\uD800(\uDC00 - 1)
	u16chars[0] = 0xD800;
	u16chars[1] = 0xDBFF;
	EXPECT_EQ(u16chars + 1, bp_u16next_codepoint(u16chars, 2, bp_little_endian, &codepoint));
	EXPECT_EQ(0xD800, codepoint);

	//\uDBFF\uDFFF
	u16chars[0] = 0xDBFF;
	u16chars[1] = 0xDFFF;
	EXPECT_EQ(u16chars + 2, bp_u16next_codepoint(u16chars, 2, bp_little_endian, &codepoint));
	EXPECT_EQ(0x10FFFF, codepoint);

	EXPECT_EQ(u16chars + 1, bp_u16next_codepoint(u16chars, 1, bp_little_endian, &codepoint));
	EXPECT_EQ(u16chars[0], codepoint);
	EXPECT_EQ(u16chars + 2, bp_u16next_codepoint(u16chars + 1, 1, bp_little_endian, &codepoint));
	EXPECT_EQ(u16chars[1], codepoint);

	//(\uDBFF + 1)\uDFFF
	u16chars[0] = 0xDC00;
	u16chars[1] = 0xDFFF;
	EXPECT_EQ(u16chars + 1, bp_u16next_codepoint(u16chars, 2, bp_little_endian, &codepoint));
	EXPECT_EQ(0xDC00, codepoint);

	//\uDBFF(\uDFFF + 1)
	u16chars[0] = 0xDBFF;
	u16chars[1] = 0xE000;
	EXPECT_EQ(u16chars + 1, bp_u16next_codepoint(u16chars, 2, bp_little_endian, &codepoint));
	EXPECT_EQ(0xDBFF, codepoint);
}

TEST(bp_unicode_tests, utf16_decode)
{
	bp_char32_t codepoint;
	bp_char16_t zerou16 = 0;
	EXPECT_EQ(&zerou16 + 1, bp_u16next_codepoint(&zerou16, 1, bp_little_endian, &codepoint));
	EXPECT_EQ(0, codepoint);

	auto tables = createTestUnicodeTable();
	std::wstring u16str = unicodeTablesToUTF16String(tables, true, false);

	size_t pos = 0;
	for (auto iter = tables.begin(); iter != tables.end(); ++iter)
	{
		const bp_char16_t *u16chars = (const bp_char16_t *)u16str.c_str() + pos;
		auto next = bp_u16next_codepoint(u16chars, u16str.size() - pos, bp_little_endian, &codepoint);
		EXPECT_EQ(iter->u16.size(), next - u16chars);
		EXPECT_EQ(iter->codepoint, codepoint);
		pos += next - u16chars;
	}
}

TEST(bp_unicode_tests, utf8_next)
{
	auto tables = createTestUnicodeTable();
	string u8str = unicodeTablesToUTF8String(tables, false);
	const bp_char8_t *ptr = (const bp_char8_t *)u8str.c_str();

	bp_char32_t value = {0};
	EXPECT_EQ(nullptr, bp_u8next(nullptr, u8str.size(), &value));
	EXPECT_EQ(nullptr, bp_u8next(ptr, 0, &value));

	size_t offset = 0;
	for (auto iter = tables.begin(); iter != tables.end(); ++iter)
	{
		auto next = bp_u8next(ptr + offset, u8str.size() - offset, &value);
		EXPECT_EQ(ptr + offset + iter->u8.size(), next);
		EXPECT_EQ(iter->u8char, value);

		offset += next - ptr - offset;
	}

	offset = 0;
	for (auto iter = tables.begin(); iter != tables.end(); ++iter)
	{
		auto next = bp_u8next(ptr + offset, u8str.size() - offset, nullptr);
		EXPECT_EQ(ptr + offset + iter->u8.size(), next);

		offset += next - ptr - offset;
	}

	auto invalids = createInvalidUTF8Strings();
	for (auto iter = invalids.begin(); iter != invalids.end(); ++iter)
	{
		EXPECT_EQ(nullptr, bp_u8next((const bp_char8_t *)iter->c_str(), iter->size(), &value));
	}

	//BOM UTF8
	string u8bomstr = unicodeTablesToUTF8String(tables, true);
	ptr = (const bp_char8_t *)u8bomstr.c_str();
	offset = 0;
	for (auto iter = tables.begin(); iter != tables.end(); ++iter)
	{
		auto next = bp_u8next(ptr + offset, u8bomstr.size() - offset, &value);
		auto expectPos = ptr + offset + iter->u8.size();
		if (offset == 0)
		{
			expectPos += getUTF8Bom().size();
		}
		EXPECT_EQ(expectPos, next);
		EXPECT_EQ(iter->u8char, value);

		offset += next - ptr - offset;
	}
}

TEST(bp_unicode_tests, utf8_encode_null_input)
{
	bp_char32_t u8;
	EXPECT_EQ(4, bp_u8encode(0x10FFFF, true, nullptr));
	EXPECT_EQ(4, bp_u8encode(0x10FFFF, true, nullptr));
	EXPECT_EQ(4, bp_u8encode(0x10FFFF, true, &u8));
	EXPECT_EQ(4, bp_u8encode(0x10FFFF, true, &u8));
}

TEST(bp_unicode_tests, utf8_encode)
{
	bp_char32_t u8;
	EXPECT_EQ(1, bp_u8encode(0, true, &u8));
	EXPECT_EQ(0, u8);

	EXPECT_EQ(0, bp_u8encode(0x110000, true, &u8));

	auto tables = createTestUnicodeTable();
	for (auto iter = tables.begin(); iter != tables.end(); ++iter)
	{
		size_t count = bp_u8encode(iter->codepoint, true, &u8);
		EXPECT_EQ(iter->u8.size(), count);
		EXPECT_EQ(iter->u8char, u8);
	}

	for (auto iter = tables.begin(); iter != tables.end(); ++iter)
	{
		size_t count = bp_u8encode(iter->bigEndianCodepoint, false, &u8);
		EXPECT_EQ(iter->u8.size(), count);
		EXPECT_EQ(iter->u8char, u8);
	}
}

TEST(bp_unicode_tests, utf8_decode_null_input)
{
	bp_char32_t u8 = 0x41;
	bp_char32_t codepoint = 0;
	EXPECT_EQ((bp_char8_t *)&u8 + 1, bp_u8next_codepoint((bp_char8_t *)&u8, 1, nullptr));
	EXPECT_EQ(nullptr, bp_u8next_codepoint((bp_char8_t *)&u8, 0, &codepoint));
	EXPECT_EQ(nullptr, bp_u8next_codepoint(nullptr, 1, &codepoint));
}

TEST(bp_unicode_tests, utf8_decode_invalid_input)
{
	bp_char32_t codepoint;
	auto invalids = createInvalidUTF8Strings();
	for (auto iter = invalids.begin(); iter != invalids.end(); ++iter)
	{
		const bp_char8_t *ptr = (const bp_char8_t *)iter->c_str();
		EXPECT_EQ(nullptr, bp_u8next_codepoint(ptr, iter->size(), &codepoint));
	}

	//b11110xxx, b10xxxxxx, b10xxxxxx, b10xxxxxx
	bp_char32_t u8 = 0x808080F0;
	EXPECT_EQ((const bp_char8_t *)&u8 + 4, bp_u8next_codepoint((const bp_char8_t *)&u8, 4, &codepoint));
	EXPECT_EQ(nullptr, bp_u8next_codepoint((const bp_char8_t *)&u8, 3, &codepoint));
}

TEST(bp_unicode_tests, utf8_decode)
{
	bp_char32_t codepoint;
	bp_char32_t u8 = 0;
	EXPECT_EQ((const bp_char8_t *)&u8 + 1, bp_u8next_codepoint((const bp_char8_t *)&u8, 4, &codepoint));
	EXPECT_EQ(0, codepoint);

	auto tables = createTestUnicodeTable();
	string utf8String = unicodeTablesToUTF8String(tables, false);
	const bp_char8_t *ptr = (const bp_char8_t *)utf8String.c_str();
	size_t offset = 0;
	for (auto iter = tables.begin(); iter != tables.end(); ++iter)
	{
		auto next = bp_u8next_codepoint(ptr + offset, utf8String.size() - offset, &codepoint);
		EXPECT_NE(nullptr, next);
		//EXPECT_EQ(iter->u8.size(), decoded);
		EXPECT_EQ(iter->codepoint, codepoint);
		offset += next - ptr - offset;
	}
}

TEST(bp_unicode_tests, utf8_to_utf16_tests) 
{
	auto tables = createTestUnicodeTable();
	wstring utf16String = unicodeTablesToUTF16String(tables, true, false);
	size_t bufsize = utf16String.size();
	bp_char16_t *buf = new bp_char16_t[bufsize];
	unique_ptr<bp_char16_t> _tmp(buf);

	auto utf8vec = unicodeTablesToTestUTF8StringList(tables);
	for (auto u8vec_iter = utf8vec.begin(); u8vec_iter != utf8vec.end(); ++u8vec_iter)
	{
		EXPECT_EQ(0, bp_u8_to_u16(nullptr, u8vec_iter->size(), buf, bufsize));
		EXPECT_EQ(0, bp_u8_to_u16(u8vec_iter->ptr(), 0, buf, bufsize));

		EXPECT_EQ(utf16String.size(), bp_u8_to_u16(u8vec_iter->ptr(), u8vec_iter->size(), nullptr, bufsize));
		EXPECT_EQ(utf16String.size(), bp_u8_to_u16(u8vec_iter->ptr(), u8vec_iter->size(), buf, 0));

		EXPECT_EQ(utf16String.size(), bp_u8_to_u16(u8vec_iter->ptr(), u8vec_iter->size(), buf, bufsize));
		EXPECT_EQ(utf16String, wstring(buf, buf + utf16String.size()));

		size_t lesscount = 0;
		for (auto iter = tables.rbegin(); iter != tables.rend(); ++iter)
		{
			lesscount += iter->u16.size();
			size_t count = utf16String.size() - lesscount;
			if (count == 0) {
				break;
			}

			EXPECT_EQ(count, bp_u8_to_u16(u8vec_iter->ptr(), u8vec_iter->size(), buf, count));

			wstring u16substr = utf16String.substr(0, count);
			EXPECT_EQ(u16substr, wstring(buf, buf + count));
		}
	}
}

TEST(bp_unicode_tests, utf16_to_utf8_tests)
{
	auto tables = createTestUnicodeTable();
	string utf8String = unicodeTablesToUTF8String(tables, false);
	size_t bufsize = utf8String.size();
	bp_char8_t *buf = new bp_char8_t[bufsize];
	unique_ptr<bp_char8_t> _tmp(buf);

	auto utf16vec = utf16_test_string::unicodeTablesToTestUTF16StringList(tables);
	for (auto iter = utf16vec.begin(); iter != utf16vec.end(); ++iter)
	{
		EXPECT_EQ(0, bp_u16_to_u8(nullptr, iter->size(), iter->byte_order, buf, bufsize));
		EXPECT_EQ(0, bp_u16_to_u8(iter->ptr(), 0, iter->byte_order, buf, bufsize));

		EXPECT_EQ(utf8String.size(), bp_u16_to_u8(iter->ptr(), iter->size(), iter->byte_order, nullptr, bufsize));
		EXPECT_EQ(utf8String.size(), bp_u16_to_u8(iter->ptr(), iter->size(), iter->byte_order, buf, 0));

		EXPECT_EQ(utf8String.size(), bp_u16_to_u8(iter->ptr(), iter->size(), iter->byte_order, buf, bufsize));
		EXPECT_EQ(utf8String, string(buf, buf + utf8String.size()));

		size_t lesscount = 0;
		for (auto table_iter = tables.rbegin(); table_iter != tables.rend(); ++table_iter)
		{
			lesscount += table_iter->u8.size();
			size_t count = utf8String.size() - lesscount;
			if (count == 0) {
				break;
			}

			EXPECT_EQ(count, bp_u16_to_u8(iter->ptr(), iter->size(), iter->byte_order, buf, count));

			string u8substr = utf8String.substr(0, count);
			EXPECT_EQ(u8substr, string(buf, buf + count));
		}
	}
}